﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using Affinity.Synthesis.Collections;

namespace Affinity.Synthesis.ExpressionCells.Basis
{
    /// <summary>
    /// Expression cell representing a Select statement, optimized to prevent unnecessary calls to the Select statement's delegate. 
    /// </summary>
    internal abstract class OptimizedSelectExpressionCell : QueryMethodInvocationCell
    {
        /// <summary>
        /// Constructor accepting a MethodCallExpression representing a Select statement and the delegate for the method provided
        /// to the Select statement.
        /// </summary>
        /// <param name="methodCallExpression"></param>
        /// <param name="methodDelegate"></param>
        protected OptimizedSelectExpressionCell(MethodCallExpression methodCallExpression, Delegate methodDelegate)
            : base(methodCallExpression, methodDelegate)
        {
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected OptimizedSelectExpressionCell(OptimizedSelectExpressionCell existing)
            : base(existing)
        {
        }

        /// <summary>
        /// Factory method for creating an OptimizedSelectExpressionCell from the given MethodCallExpression representing
        /// a Select statement and the delegate representing the method provided to the Select statement.
        /// </summary>
        /// <param name="methodCallExpression"></param>
        /// <param name="methodDelegate"></param>
        /// <returns></returns>
        public static new OptimizedSelectExpressionCell Create(MethodCallExpression methodCallExpression, Delegate methodDelegate)
        {
            Type[] methodGenericArguments = methodCallExpression.Method.GetGenericArguments();
            Type sourceElementType = methodGenericArguments[0];
            Type targetElementType = methodGenericArguments[1];

            return (OptimizedSelectExpressionCell)Activator.CreateInstance(typeof(OptimizedSelectExpressionCell<,>)
                .MakeGenericType(sourceElementType, targetElementType), methodCallExpression, methodDelegate);
        }
    }

    /// <summary>
    /// Expression cell representing a Select statement, optimized to prevent unnecessary calls to the Select statement's delegate. 
    /// </summary>
    internal class OptimizedSelectExpressionCell<TSource, TTarget> : OptimizedSelectExpressionCell
    {
        /// <summary>
        /// Constructor accepting a MethodCallExpression representing a Select statement and the delegate for the method provided
        /// to the Select statement.
        /// </summary>
        /// <param name="methodCallExpression"></param>
        /// <param name="methodDelegate"></param>
        public OptimizedSelectExpressionCell(MethodCallExpression methodCallExpression, Delegate methodDelegate)
            : base(methodCallExpression, methodDelegate)
        {
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected OptimizedSelectExpressionCell(OptimizedSelectExpressionCell<TSource, TTarget> existing)
            : base(existing)
        {
        }

        /// <summary>
        /// Creates and returns a copy of this expression cell.
        /// </summary>
        /// <returns></returns>
        public override IExpressionCell Copy()
        {
            return new OptimizedSelectExpressionCell<TSource, TTarget>(this);
        }

        /// <summary>
        /// "Calculates" the output of this expression cell. Uses a MappedCollection to implement the Select mapping.
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="hasValue"></param>
        /// <param name="value"></param>
        protected override void CalculateOutput(object[] inputs, out bool hasValue, out object value)
        {
            if (inputs.Length != 2) throw new ArgumentException("Expecting two input values.");
            if (inputs[0] != null && !(inputs[0] is IEnumerable<TSource>)) throw new ArgumentException(string.Format("Expecting IEnumerable<{0}> or null as first input value.", typeof(TSource)));
            
            if (_Collection == null)
            {
                if (!(inputs[1] is Func<TSource, TTarget>)) throw new ArgumentException(string.Format("Expecting Func<{0}, {1}> as second input value.", typeof(TSource), typeof(TTarget))); 
                _Collection = new MappedCollection<TSource, TTarget>((Func<TSource, TTarget>)inputs[1]);
            }

            SetTrackedItems(inputs.OfType<IEnumerable>().SelectMany(
                enumerable => enumerable.OfType<INotifyPropertyChanged>()).ToArray());

            SetNotifyCollectionChangedSources(inputs.OfType<INotifyCollectionChanged>().ToArray());

            _Collection.SourceItems = (IEnumerable<TSource>)inputs[0];

            hasValue = true;

            value = _Collection;
        }

        private MappedCollection<TSource, TTarget> _Collection = null;
    }
}
